﻿namespace Ymatou.UserAuth.Agent.Sso
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Web;
    using Ymatou.CommonService;
    using Ymatou.User.Dto.Auth;
    using Ymatou.User.ServerResponse;
    using Ymatou.UserAuth.Extension;

    /// <summary>
    /// sso client 
    /// </summary>
    public class SSOClient
    {
        /// <summary>
        /// 返回用户解密后的token 
        /// </summary>
        /// <param name="decrypt"></param>
        /// <returns></returns>
        public static string GetUserClientToken(bool decrypt = true)
        {
            var userToke = HttpContext.Current.Request.ReadCookie(ConstParameter.SSOCOOKIEKEY_CLIENT_SIGN);
            if (string.IsNullOrEmpty(userToke) || userToke.ToLower() == "null") return string.Empty;
            if (!decrypt)
                return userToke;
            else
                return TryDecryptString(userToke);
        }

        /// <summary>
        /// 是否存在 cookie
        /// </summary>
        /// <returns></returns>
        public static bool IsExistsToken()
        {
            var userToke = HttpContext.Current.Request.ReadCookie(ConstParameter.SSOCOOKIEKEY_CLIENT_SIGN);
            if (string.IsNullOrEmpty(userToke) || userToke.ToLower() == "null") return false;
            return true;
        }
        /// <summary>
        /// 校验token 
        /// </summary>
        /// <param name="token"></param>
        /// <param name="tokenVerify"></param>
        /// <param name="signVerify"></param>
        /// <param name="seccessAction">签名校验成功后callback</param>
        /// <param name="redirectLoginPage">是否重定向到LoginPage</param>
        /// <param name="redirectOpenAuth">是否重定向到openAuth</param>
        /// <param name="signCheckfailAction">认证失败执行callback，默认跳转到登陆页</param>
        /// <param name="loginAtion">自定义登录逻辑</param>
        public static void CheckSign(string token
           , Func<string, ResponseData<TokenResponseDto>> tokenVerify
           , Func<string, string, bool> signVerify
           , Action<int> seccessAction
           , bool redirectLoginPage
           , bool redirectOpenAuth
           , Action signCheckfailAction = null
           , Action loginAtion = null)
        {
            //开关检查
            if (!CheckSsoOPen()) return;

            if (!string.IsNullOrEmpty(token) && token.ToLower() != "null")
            {
                try
                {
                    var tokenArry = token.Split(new char[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
                    var isRedirectLoginPage = (tokenArry.Length != 3);
                    //if (tokenArry.Length != 3)
                    //{
                    //    isRedirectLoginPage = true;
                    //}
                    //else
                    //{
                    //    isRedirectLoginPage = false;
                    //}
                    var _userId = Convert.ToInt32(tokenArry[0]);
                    var _token = tokenArry[1];
                    //var tokenVerifyResult = tokenVerify(token);
                    //isRedirectLoginPage = !tokenVerifyResult.Success;
                    //if (tokenVerifyResult.Success)
                    //{
                    //2, 签名认证
                    var sign = new SignInfo(_token, _userId);
                    sign.GenerateSign();
                    var signVerifyResult = signVerify(sign.OriginalVal, sign.Sign);
                    isRedirectLoginPage = !signVerifyResult;
                    if (signVerifyResult)
                    {
                        seccessAction(_userId);
                    }
                    else
                    {
                        //执行自定义错误回调
                        if (signCheckfailAction != null) signCheckfailAction();
                        //如果未指定签名错误回调，则强制清除用户当前session
                        else
                            RemoveUserSession();
                    }
                    //}
                    if (isRedirectLoginPage)
                    {
                        if (!redirectLoginPage) return;

                        if (loginAtion != null)
                            loginAtion();
                        else
                            RedirectLoginPage();
                    }
                }
                catch (Exception ex)
                {
                    ApplicationLog.Error("客户端校验token异常，", ex);
                }
            }
            else
            {
                //login,loginresult,products,register
                var path = HttpContext.Current.Request.RawUrl;
                if (path.ToLower() == "/" || path.ToLower().Contains("login")) return;
                if (path.ToLower() == "clientcallbackhandler.ashx") return;
                if (path.ToLower().Contains("register") || path.ToLower().Contains("products")) return;
                //客户端不允许匿名访问，且本地无cookie， 跳转到OpenAuth 
                if (redirectOpenAuth)
                {
                    RedirectOpenAuthSite();
                    return;
                }
                else
                {
                    if (redirectLoginPage)
                    {
                        loginAtion();
                        return;
                    }
                }
            }
        }

        /// <summary>
        /// 校验用户cookie token
        /// </summary>
        ///<param name="tokenVerify">token 验证</param>
        ///<param name="signVerify">参数1：原始窜，参数2 ：加密窜</param>
        ///<param name="seccessAction">签名校验成功后callback</param>
        ///<param name="redirectLoginPage">是否重定向到登录页</param>
        ///<param name="redirectOpenAuth">是否重定向到OpenAuth</param>
        ///<param name="failAction">认证失败执行callback，默认跳转到登陆页</param>
        public static void CheckSign(

              Func<string, ResponseData<TokenResponseDto>> tokenVerify
            , Func<string, string, bool> signVerify
            , Action<int> seccessAction
            , bool redirectLoginPage
            , bool redirectOpenAuth
               , Action signCheckfailAction = null
           , Action loginAtion = null)
        {
            //var cookieValue_token = GetUserClientToken();
            CheckSign(GetUserClientToken(), tokenVerify, signVerify, seccessAction, redirectLoginPage, redirectOpenAuth, signCheckfailAction, loginAtion);
        }

        private static void RedirectLoginPage()
        {
            var noVerifyTokenPath = System.Configuration.ConfigurationManager.AppSettings["NoRedirectLoginPage"].Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
            var rawUrl = HttpContext.Current.Request.RawUrl.ToLower();
            var noVerifyUrl = noVerifyTokenPath.Any(e => rawUrl.EndsWith(e) && rawUrl != "/");
            if (noVerifyUrl)
            {
                return;
            }
            //~/home/login/
            var loginUrl = System.Configuration.ConfigurationManager.AppSettings["LoginUrl"] ?? "/";
            //当前请求参数

            var rUrl = HttpContext.Current.Request.Url.ToString().RemoveQueryStringKey(ConstParameter.QueryToken).RemoveQueryStringKey(ConstParameter.ReturnUrl);
            var q = new Uri(rUrl);

            var url = loginUrl;
            if (!string.IsNullOrEmpty(q.Query))
                url = string.Format("{0}?{1}", loginUrl, q.Query);
            HttpContext.Current.Response.Redirect(url);
        }
        private static void RedirectOpenAuthSite()
        {
            var openAuthHost = System.Configuration.ConfigurationManager.AppSettings["OpenAuthHost"] ?? "/";
            if (string.IsNullOrEmpty(openAuthHost))
                throw new ArgumentNullException("sso site url null??");
            var redirectUrl = string.Format("{0}?{1}={2}", openAuthHost, ConstParameter.ReturnUrl, HttpContext.Current.Request.Url.ToString());
            HttpContext.Current.Response.Redirect(redirectUrl);
        }
        /// <summary>
        /// 用户登出，在用户登出及session_end 事件中调用
        /// </summary>
        public static string SSOLogOut(bool script = true)
        {
            if (!CheckSsoOPen()) return string.Empty;
            privateLogOut();
            if (!script) return string.Empty;
            return LogOutScript();
        }

        /// <summary>
        /// sso login
        /// </summary>
        /// <param name="sign"></param>
        public static void SSOLogin(string sign, TimeSpan? tokenTimeOut = null)
        {
            //SSO 开关，登录是否成功判断
            if (!CheckSsoOPen()) return;
            //登录成功 服务端返回 加密的token

            if (string.IsNullOrEmpty(sign)) return;
            var _timeOut = tokenTimeOut.HasValue ? (DateTime?)DateTime.Now.Add(tokenTimeOut.Value) : null;
            HttpContext.Current.Response.SetCookie(ConstParameter.SSOCOOKIEKEY_CLIENT_SIGN, sign, domain: SSOClient.SubDomain(HttpContext.Current.Request.Url.Host), httpOnly: true, expires: _timeOut);
            //HttpContext.Current.Response.SetCookie(ConstParameter.SSOCOOKIEKEY_CLIENT_USERFLAG, loginId, domain: SSOClient.SubDomain(HttpContext.Current.Request.Url.Host), httpOnly: true, expires: _timeOut);

        }

        public static void SetUserTokenCookie(string token, TimeSpan? tokenTimeOut = null)
        {
            if (!CheckSsoOPen() || string.IsNullOrEmpty(token)) return;
            if (HttpContext.Current == null) return;
            var _timeOut = tokenTimeOut.HasValue ? (DateTime?)DateTime.Now.Add(tokenTimeOut.Value) : null;
            HttpContext.Current.Response.SetCookie(ConstParameter.SSOCOOKIEKEY_CLIENT_SIGN, token, domain: SSOClient.SubDomain(HttpContext.Current.Request.Url.Host), httpOnly: true, expires: _timeOut);
        }
        /// <summary>
        /// 获取访问cookie域
        /// </summary>
        /// <param name="host"></param>
        /// <param name="defValHost"></param>
        /// <returns></returns>
        public static string SubDomain(string host, string defValHost = null)
        {
            if (string.IsNullOrEmpty(host)) return defValHost;
            var _host = host.Split(new char[] { '.' }, StringSplitOptions.RemoveEmptyEntries);
            var _subHost = new StringBuilder(".");
            for (var i = 1; i < _host.Length; i++)
            {
                _subHost.AppendFormat("{0}.", _host[i]);
            }
            return _subHost.ToString().TrimEnd('.');
        }
        /// <summary>
        /// 检查开关
        /// </summary>
        /// <returns></returns>
        public static bool CheckSsoOPen()
        {
            return System.Configuration.ConfigurationManager.AppSettings["SsoOpen"] == "1";
        }

        private static string TryDecryptString(string userToke)
        {
            try
            {
                return new Encrypt().DesDecrypt(userToke, true);
            }
            catch (Exception ex)
            {
                ApplicationLog.Error(string.Format("{0}客户端解密token异常", userToke), ex);
                return string.Empty;
            }
        }

        private static bool IsAllowAnonymousView(string currentPath)
        {
            if (string.IsNullOrEmpty(currentPath) || currentPath == "/") return true;
            var allowAnonymous = System.Configuration.ConfigurationManager.AppSettings["AllowAnonymousView"];
            if (string.IsNullOrEmpty(allowAnonymous)) return true;

            var allowAnonymousArr = allowAnonymous.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
            var _currentPath = currentPath.ToLower();
            return allowAnonymousArr.Where(e => _currentPath.Contains(e)).Any();
        }

        private static void privateLogOut()
        {
            HttpContext.Current.Response.RemoveCookie("ymt_login");
            HttpContext.Current.Response.RemoveCookie(ConstParameter.SSOCOOKIEKEY_CLIENT_SIGN, SSOClient.SubDomain(HttpContext.Current.Request.Url.Host));
        }

        private static string LogOutScript()
        {
            var logOutUrl = System.Configuration.ConfigurationManager.AppSettings["OpenAuthLogOutHost"];
            var script = new StringBuilder();
            script.Append("var _r = Math.random();");
            script.AppendFormat("$.getScript('{0}?r=' + _r, function () {1}", logOutUrl, "{");
            script.Append("  logOut();");
            script.Append(" });");
            return script.ToString();
        }
        public static void RemoveUserSession(string key = null)
        {
            if (string.IsNullOrEmpty(key))
            {
                HttpContext.Current.Session.Remove("UserEntity");
                HttpContext.Current.Session.Remove("UserInfo");
                SSOLogOut(false);
            }
            else
            {
                HttpContext.Current.Session.Remove(key);
            }
        }
    }

    /// <summary>
    /// 签名
    /// </summary>
    internal class SignInfo
    {
        /// <summary>
        /// 私钥
        /// </summary>
        // public const string privateKey = "<RSAKeyValue><Modulus>yJlVOeahWIm8qv0Q65fyl58OVhyd3UrkwpfH2lyxsHvHzOJmaeP5BiPcG0/7wxdiqDvAFzYNrLdkgBfBcvyl9OzAOTV25wQPygRAzydfqclFP/Qsx1BnzBp/RvjNIyqKavaqpVijAAhaCfFE+Gv22FeT9PkFEYmuDK82cTIyYFs=</Modulus><Exponent>AQAB</Exponent><P>8XeN5KteWvmgT2EewQ4VO9OiC4rlacA5fHQJpV+E59JpmGxlcrLFhBolgOaeGgD5Tt1py68ZluDIzYg4hfRm/w==</P><Q>1KwYzaq7Pp9pplnliv/7jXffu+sQH3a9VGvLbXfLZHNsi+50VdYj1O3D7MLSqI9WNINIQoEOUDfer5W7uyICpQ==</Q><DP>vWnMC7GF7ZVddIc5ybTTYT/NIILPHRplvcOEgY4hQnxiyudU2Rl1KQAdpTTClqFm779tqxJq31jrC5csx5+FXw==</DP><DQ>bt1YAL7j5X1Mof9y/cb75396Dog3W2+Wuw+GeIZebPsOttOTqbt76mAYJrBZCK3QDofxf1mvkHnPiFDSEHdmUQ==</DQ><InverseQ>NFCFTyPQkHE88ObRztW7hh9EwPRStFEcdXGTEsqpjaSXvzDkFTYv3xRfBi4KzGH6XHX5Rtzs3q7tBBCke5SqVQ==</InverseQ><D>puPKoA1clsihCrDYbulPjw1pZAMS45T3lV2pi+YqfuW8aJzDD7325EDZUm/MyZafIc/0FFuILPW2y0r4Sq4fcpfhE5O8qqm0ejya2xZ0YJI2OR4CS60o14oU+TGUvwbkj9wZPwZ4bf8WmK5lqwwonhXNQBMXZ/k3yaVP8kK2PSE=</D></RSAKeyValue>";
        /// <summary>
        /// 公钥
        /// </summary>
        private const string publicKey = "<RSAKeyValue><Modulus>yJlVOeahWIm8qv0Q65fyl58OVhyd3UrkwpfH2lyxsHvHzOJmaeP5BiPcG0/7wxdiqDvAFzYNrLdkgBfBcvyl9OzAOTV25wQPygRAzydfqclFP/Qsx1BnzBp/RvjNIyqKavaqpVijAAhaCfFE+Gv22FeT9PkFEYmuDK82cTIyYFs=</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>";
        /// <summary>
        /// 隐藏窜
        /// </summary>
        private const string HideKey = "_ymatou_com";

        public string OriginalVal { get; private set; }
        public string Sign { get; private set; }

        public SignInfo(string token, int userId)
        {
            this.OriginalVal = string.Format("{0}|{1}", userId, token);
        }

        public void GenerateSign()
        {
            bool error;
            var _val = string.Format("{0}|{1}", this.OriginalVal, HideKey);
            this.Sign = EncryptorHelp.RSAGroupingEncryptor(publicKey, _val, Encoding.UTF8, out error);
            if (error)
            {
                ApplicationLog.Error(string.Format("SignInfo 签名错误 {0}", Sign));
            }
        }
    }
}
